﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using VisualStudioProvider;
using System.IO;
using Utils;
using System.Threading;
using AbstraX;

namespace BuildSweeper
{
    class Program
    {
        private static string programOutput;

        private static List<string> EnvironmentVariables
        {
            get
            {
                return new List<string>()
                {
                    "SFSOLUTIONPATH",
                    "SDKPATH",
                    "SILVERLIGHTPATH",
                    "VSSP1PATH",
                    "VSCOMMON",
                    "FrameworkDir"
                };
            }
        }

        private static List<string> EnvironmentPaths
        {
            get
            {
                var list = new List<string>();

                EnvironmentVariables.ForEach(e => Environment.GetEnvironmentVariable(e, EnvironmentVariableTarget.Machine).Process(v => list.Add((string)v)));

                return list;
            }
        }

        static void Main(string[] args)
        {
            var indent = 0;
            var errorsFound = 0;
            var zero = 0;
            var one = 1;
            var parserError = new StringBuilder();

            OutGreen(ref indent, "Sweeping project files...\r\n");

            var solutionPath = args[0];

            VSSolution.OnParserError += (sender, e) =>
            {
                parserError.AppendLine(e.Value.Message);
            };

            var solution = new VSSolution(Path.Combine(solutionPath, "SolutionFramework.sln"));

            OutNormal(ref indent, "Analyzing solution: " + solution.Name);
            indent++;

            var solutionFolders = solution.Projects.Where(p => p.ProjectType == "SolutionFolder").Select(p => p.FileName).Concat
                (
                    solution.Projects.SelectMany(p => p.Items.Where(i => i.ItemType == "Folder").Select(i => Path.GetFullPath(Path.Combine(new FileInfo(p.FileName).DirectoryName, i.RelativePath))))

                ).Concat
                    (
                        solution.Projects.Select(p => new FileInfo(p.FileName).DirectoryName)

                    ).Concat
                        (
                            new List<string> { solutionPath }
                        )
                        .Distinct();

            foreach (VSProject project in solution.Projects.OrderBy(p => p.Name))
            {
                var projectPath = new FileInfo(project.FileName).DirectoryName;

                OutNormal(ref indent, "Analyzing project: " + project.Name);
                indent++;

                foreach (VSProjectItem item in project.Items)
                {
                    if (item.IsFile)
                    {
                        if (item.RelativePath.Contains("Program Files (x86)"))
                        {
                            var output = string.Format("\r\nInvalid item path for {0}\\{1}.\r\n", project.Name, item.Name);
                            OutRed(ref zero, output);
                            FileOut(ref one, string.Format("Path: {0}", item.RelativePath));

                            errorsFound++;
                        }
                        else if (item.RelativePath.StartsWith(@"..\"))
                        {
                            try
                            {
                                var path = Path.GetFullPath(Path.Combine(projectPath, item.RelativePath));

                                if (!solutionFolders.Any(f => path.StartsWith(f)) && !EnvironmentPaths.Any(f => path.StartsWith(f)))
                                {
                                    var output = string.Format("\r\nInvalid item path for {0}\\{1}.\r\n", project.Name, item.Name);
                                    OutRed(ref zero, output);
                                    FileOut(ref one, string.Format("Path: {0}", item.RelativePath));

                                    errorsFound++;
                                }
                            }
                            catch
                            {
                                var output = string.Format("\r\nInvalid item path for {0}\\{1}.\r\n", project.Name, item.Name);
                                OutRed(ref zero, output);
                                FileOut(ref one, string.Format("Path: {0}", item.RelativePath));

                                errorsFound++;
                            }

                        }
                        else if (DriveInfo.GetDrives().Any(d => item.RelativePath.StartsWith(d.Name)))
                        {
                            var output = string.Format("\r\nInvalid item path for {0}\\{1}.\r\n", project.Name, item.Name);
                            OutRed(ref zero, output);
                            FileOut(ref one, string.Format("Path: {0}", item.RelativePath));

                            errorsFound++;
                        }
                    }
                    else if (item.ItemType == "Reference")
                    {
                        if (item.HasMetadata)
                        {
                            foreach (var metaData in item.Metadata)
                            {
                                if (metaData.Name == "HintPath")
                                {
                                    if (metaData.Value.Contains("Program Files (x86)"))
                                    {
                                        var output = string.Format("\r\nInvalid hint path for {0}\\{1}.\r\n", project.Name, item.Name);
                                        OutRed(ref zero, output);
                                        FileOut(ref one, string.Format("Path: {0}", metaData.Value));

                                        errorsFound++;
                                    }
                                    else if (metaData.Value.StartsWith(@"..\"))
                                    {
                                        try
                                        {
                                            var path = Path.GetFullPath(Path.Combine(projectPath, metaData.Value));

                                            if (!solutionFolders.Any(f => path.StartsWith(f)) && !EnvironmentPaths.Any(f => path.StartsWith(f)))
                                            {
                                                var output = string.Format("\r\nInvalid hint path for {0}\\{1}.\r\n", project.Name, item.Name);
                                                OutRed(ref zero, output);
                                                FileOut(ref one, string.Format("Path: {0}", metaData.Value));

                                                errorsFound++;
                                            }
                                        }
                                        catch
                                        {
                                            var output = string.Format("\r\nInvalid hint path for {0}\\{1}.\r\n", project.Name, item.Name);
                                            OutRed(ref zero, output);
                                            FileOut(ref one, string.Format("Path: {0}", metaData.Value));

                                            errorsFound++;
                                        }
                                    }
                                    else if (DriveInfo.GetDrives().Any(d => metaData.Value.StartsWith(d.Name)))
                                    {
                                        var output = string.Format("\r\nInvalid hint path for {0}\\{1}.\r\n", project.Name, item.Name);
                                        OutRed(ref zero, output);
                                        FileOut(ref one, string.Format("Path: {0}", metaData.Value));

                                        errorsFound++;
                                    }
                                }
                            }
                        }
                    }
                }

                indent--;
            }

            indent--;
            Console.WriteLine();

            if (errorsFound > 0)
            {
                var outputPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "BuildViolations.txt");

                using (var outputFile = File.Open(outputPath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
                {
                    outputFile.Write(ASCIIEncoding.ASCII.GetBytes(programOutput), 0, programOutput.Length);
                    outputFile.Flush();
                }

                var list = EnvironmentVariables.ToCommaDelimitedList((item, isLast) => 
                {
                    if (isLast)
                    {
                        return "or " + item;
                    }
                    else
                    {
                        return item + " ";
                    }
                });

                OutYellow(ref indent, "Mind Chemistry(r) programming standards\r\n");
                OutYellow(ref indent, string.Format("{0} violations found. Paths should always be a relative path to the solution or start with the {1} environment variable. Please edit project files and fix before checking in.\r\n", errorsFound, list));
                OutYellow(ref indent, string.Format("Output file located at: {0}\r\n", outputPath));

                OutGreen(ref indent, "Press any key to continue.");

                Console.Read();
            }
            else if (parserError.Length > 0)
            {
                OutRed(ref indent, parserError.ToString());
                OutGreen(ref indent, "Press any key to continue.");

                Console.Read();
            }
            else
            {
                OutGreen(ref indent, "No violations found. Congratulations!");
                Thread.Sleep(500);
            }
        }

        private static void OutRed(ref int indent, string output)
        {
            OutColor(ref indent, ConsoleColor.Red, output);
        }

        private static void FileOut(ref int indent, string output)
        {
            var indentedOut = new string('\t', indent) + output;

            programOutput += "\r\n" + indentedOut + "\r\n\r\n";
        }

        private static void OutGreen(ref int indent, string output)
        {
            OutColor(ref indent, ConsoleColor.Green, output);
        }

        private static void OutYellow(ref int indent, string output)
        {
            OutColor(ref indent, ConsoleColor.DarkYellow, output);
        }

        private static void OutColor(ref int indent, ConsoleColor color, string output)
        {
            using (var colorizer = new ConsoleColorizer(color))
            {
                var indentedOut = new string('\t', indent) + output;

                programOutput += indentedOut + "\r\n";

                Console.WriteLine(indentedOut);
            }
        }

        private static void OutNormal(ref int indent, string output)
        {
            var indentedOut = new string('\t', indent) + output;

            programOutput += indentedOut + "\r\n";

            Console.WriteLine(indentedOut);
        }
    }
}
